/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author vimi Administrator
 *  Dinh Ngoc 10.08.2010
 *
 */

import model.Course;
import model.Room;
import model.Pair;
import model.Triple;
import model.Curriculum;

import stats.CustomPlotChart;

import util.ObjectiveFunction;

import java.util.List;
import java.util.ArrayList;
import java.util.Date;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Category;
import org.apache.log4j.PropertyConfigurator;
import java.util.Properties;
import java.io.IOException;
import java.io.StringWriter;

import org.apache.log4j.FileAppender;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;

import org.apache.log4j.PatternLayout;
import org.apache.log4j.PropertyConfigurator;


import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.xy.DefaultXYDataset;

import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;

import etm.core.configuration.EtmManager;
import etm.core.monitor.EtmMonitor;
import etm.core.monitor.EtmPoint;
import etm.core.configuration.BasicEtmConfigurator;
import etm.core.renderer.SimpleTextRenderer;

public class FirstSolFinder {
    static final Logger logger = Logger.getLogger(FirstSolFinder.class.getName());
        
    private static final EtmMonitor class_etmMonitor = EtmManager.getEtmMonitor();

    // a. Input
    public InputReader _inputReader;
    public List<Course> CourseDomain; // sorted by StudentNum
    public int[] Cs_sortedCs_Mapping=null; // [i] in CourseDomain =c in Courses
    private ArrayList<String> LecturesDomain; // sorted by StudentNum in ref to CourseDomain

    public List<Room> RoomDomain; // sorted by capacity
    public int[] Rs_sortedRs_Mapping=null; // [i] in RoomDomain =r in Rooms

    // b. redundant data structures
    private int[] intRoomDomain; // order number, sorted by capacity
    private int[] TimeslotDomain;

    // c. help semi structures
    private Pair<String,String> R_P_Pair;
    private List<Pair> R_P_Pairs;
    
    public List<Triple> L_R_P_Triples; // solution as a list of triples

    // d. output structure
    public int [][] CP_R_Solution; // CP_R_Solution[c][p] = RoomIndex | NotSet . Ref to CourseDomain
    public ObjectiveFunction assigned_ObjFunc= new ObjectiveFunction();

    // e. hill climb
    private int [][] Best_CP_R_Solution;
    private int Best_HC_OF;
    private ObjectiveFunction best_ObjFunc = new ObjectiveFunction();

    public List<Triple> HC4VioRelaxedCandidates = new ArrayList<Triple>();
    private List<Triple> HC3VioRelaxedCandidates = new ArrayList<Triple>();
    private List<Triple> HC4HC3VioRelaxedCandidates = new ArrayList<Triple>();

    public FirstSolFinder()
    {
         
    }

    private void initializeLogger(String filename)
    {
        PatternLayout layout = new PatternLayout("%m");

         logger.removeAllAppenders();

          FileAppender appender = null;
          try {
             appender = new FileAppender(layout,filename,false);
          } catch(Exception e) {}

        logger.addAppender(appender);
        logger.setLevel((Level) Level.DEBUG);
        logger.addAppender(new ConsoleAppender(layout));

    }

    public void setInputReader(InputReader inputReader)
    {
        this._inputReader = inputReader;

        LecturesDomain= new ArrayList();
        R_P_Pairs = new ArrayList();

        L_R_P_Triples = new ArrayList();
        
        initializeLogger("C:/FS_out"+this._inputReader.InputFileName+"_FirstSol.log");        
    }

    //
    public void initSolutionMatrix()
    {
        CP_R_Solution = new int[_inputReader.CNum][_inputReader.DNum*_inputReader.Periods_per_day];

        for (int i=0;i<CP_R_Solution.length;i++)
        {
            for (int j=0;j<CP_R_Solution[0].length;j++)
            {
                CP_R_Solution[i][j]=-1;

                //if (i % 2==0 && j % 2==0) CP_R_Solution[i][j]=2;

            }
        }
    }

    private void initArrayLists()
    {
        
        if (this._inputReader!= null)
        {
            Cs_sortedCs_Mapping = new int[_inputReader.Courses.size()];
            CourseDomain = Course.sortByStuNum(_inputReader.Courses,Cs_sortedCs_Mapping);
            logger.debug("Cs_sortedCs_Mapping.length"+Cs_sortedCs_Mapping.length);

            Rs_sortedRs_Mapping = new int[_inputReader.Rooms.size()];
            RoomDomain = Room.sortByCapacity(_inputReader.Rooms,Rs_sortedRs_Mapping);
            logger.debug("Rs_sortedRs_Mapping.length"+Rs_sortedRs_Mapping.length);

            /*
            CourseDomain = _inputReader.Courses;
            RoomDomain = _inputReader.Rooms;
             * 
             */

            // convert CourseDomain to LecturesDomain
            for (int i=0;i<CourseDomain.size();i++)
            {
                Course IthCourse = CourseDomain.get(i);
                //Course originalIthCourse = CourseDomain.get(i);

                int LecNum = IthCourse.getLectureNum();
                int IthCourseIndex =i;

                logger.debug(i+".Course "+IthCourse.getCourseID()+" has LecNum "+IthCourse.getLectureNum());

                for (int j=0;j<LecNum;j++)
                {

                    LecturesDomain.add(String.valueOf(IthCourseIndex));
                    logger.debug("Size of LecturesDomain"+LecturesDomain.size());
                    logger.debug(IthCourseIndex+""+" added");
                }


            }

        }
    }


    public void start()
    {
       

        // initialize measurement subsystem
        BasicEtmConfigurator.configure();
        // startup measurement subsystem
        EtmMonitor etmMonitor = EtmManager.getEtmMonitor();
        if (etmMonitor.isStarted())  etmMonitor.reset();
        etmMonitor.start();
        
        EtmPoint point = etmMonitor.createPoint("first Solution phase");


        Date StartTime = new Date(); // 1

        initArrayLists();
        initIntRoomDomain();
        initTimeslotDomain();
        generateRPPairs();
        //generateLRPTriples();
        HillClimbIterate(0);

        
        
        writeSolDown();
        // writeBestSolDown();
        // print();
        // visualize results
        Date EndTime = new Date(); // 2
        long elapsed_time = EndTime.getTime() - StartTime.getTime();
        logger.info("\n  first Solution phase----}} Time Measurement (ms): "+elapsed_time+"\n");

        point.collect();
        StringWriter writer = new StringWriter();
         //charttest
        SimpleTextRenderer mySimpleTextRenderer= new SimpleTextRenderer();
        etmMonitor.render(mySimpleTextRenderer);
        logger.info(writer.toString());
        etmMonitor.stop();

    }

    public void print()
    {
        for (int i=0;i<CourseDomain.size();i++)
        {
            Course _IthCourse = CourseDomain.get(i);

            System.out.print("Course "+_IthCourse.getCourseID()+" StuNum"+_IthCourse.getStudentNum());
            System.out.println("");

        }

        for (int i=0;i<RoomDomain.size();i++)
        {
            Room _IthRoom = RoomDomain.get(i);

            System.out.print("Room "+_IthRoom.getId()+" Capacity"+_IthRoom.getCapacity());
            System.out.println("");

        }

        // Print R_P_Pairs
        for (int i=0;i<R_P_Pairs.size();i++)
        {
            System.out.println(R_P_Pairs.get(i).getFirst()+"-"+R_P_Pairs.get(i).getSecond());
        }

        // Print LecturesDomain
        for (int i=0;i<LecturesDomain.size();i++)
        {
            logger.debug("Lecture "+LecturesDomain.get(i));
        }

        // Print L_R_P_Triple
        logger.debug("L_R_P_Triples.size()"+L_R_P_Triples.size());
        for (int i=0;i<L_R_P_Triples.size();i++)
        {
            String courseIndex = String.valueOf(L_R_P_Triples.get(i).getFirst());
            logger.debug(CourseDomain.get(Integer.parseInt(courseIndex)).getCourseID()+":"+ L_R_P_Triples.get(i).getFirst()+"-"+L_R_P_Triples.get(i).getSecond()+"-"+L_R_P_Triples.get(i).getThird());
        }


    }

    // 1. R P Pairs Generator
    // a.
    private void initIntRoomDomain()
    {
        intRoomDomain = new int[RoomDomain.size()];

        for (int i=0;i<intRoomDomain.length;i++)
        {
            intRoomDomain[i] =i;
        }
    }

    // b.
    private void initTimeslotDomain()
    {
        TimeslotDomain = new int[_inputReader.DNum*_inputReader.Periods_per_day];

        for (int i=0;i<TimeslotDomain.length;i++)
        {
            TimeslotDomain[i] =i;
        }


    }

    // c.
    private void generateRPPairs()
    {
        for (int i=0;i<intRoomDomain.length;i++)
        {
            for (int j=0;j<TimeslotDomain.length;j++)
            {
                Pair _IJPair = new Pair(intRoomDomain[i],TimeslotDomain[j]);
                R_P_Pairs.add(_IJPair);
            }
        }
    }

    // 2. Assign Lectures  with R_P_Pairs
    // a. Constraint 1, Constraint 3, Constraint 4 relax

    // @note: important
    private void generateLRPTriples()
    {
        // local variables 
        ArrayList<Pair> _R_P_Pairs = new ArrayList();
        // copy all objects from R_P_Pairs to local variable
        for (int i=0;i<R_P_Pairs.size();i++)
        {
            Pair IthPair = R_P_Pairs.get(i);

            _R_P_Pairs.add(new Pair(IthPair.getFirst(),IthPair.getSecond()));
        }
        

        ArrayList<String> _LecturesDomain = new ArrayList();
        // copy all objects from LecturesDomain to local variable
        for (int i_1=0;i_1<LecturesDomain.size();i_1++)
        {
            String IthString = LecturesDomain.get(i_1);
            _LecturesDomain.add(IthString);
            logger.debug("Size of _LecturesDomain"+_LecturesDomain.size());
            logger.debug("_LecturesDomain:"+IthString+" added");
        }

        // Parameterizise generator for first solution
        // -------------------------------------------
        boolean success= false;
        boolean timeout= false;
        int interation = 0;
        int interationMax = _R_P_Pairs.size()*LecturesDomain.size();

        boolean HC1 = false; // AllLectures
        boolean HC2 = true; // RoomOccupancy
        boolean HC3 = true; // TeacherCurriculumConflicts
        boolean HC4 = true; // Availabilities

        boolean SC1 = true; // RoomCapacity
        boolean SC2 = true; // MinimumWorkingDays
        boolean SC3 = true; // CurriculumCompactness
        boolean SC4 = true; // RoomStability
        // --------------------------------------------

        

        while (!HC1) // || !timeout
        {
            String curLecture = _LecturesDomain.get(0);
            int pre_LecturesDomainSize = _LecturesDomain.size();

            logger.debug("curLecture "+curLecture);

            // Constructing Triple - a piece in Timetable
            // Round 1: test both HC3 HC4
            for (int j=0;j<_R_P_Pairs.size();j++)
            {
                Pair _JthRPPair = _R_P_Pairs.get(j);

                HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts


                /*
                pretestHC4(curLecture,_JthRPPair);
                pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                pretestHC2(curLecture,_JthRPPair); // always satisfied
                pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                */

                if (HC4 && HC3)
                {
                  Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                  this.L_R_P_Triples.add(_IJTriple);

                  _LecturesDomain.remove(0);
                  _R_P_Pairs.remove(j);

                  logger.debug(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"test,test");
                  break;
                }
            }

            

            // Round 2: relax HC4 & test HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    // HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                    HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts


                    /*
                    pretestHC4(curLecture,_JthRPPair);
                    pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                    pretestHC2(curLecture,_JthRPPair); // always satisfied
                    pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                    */

                    if (HC3)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);

                      logger.debug(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"relax,test");
                      HC4VioRelaxedCandidates.add(_IJTriple);

                      break;
                    }
                }

            }


            // Round 3: test HC4 & relax HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                    //HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts


                    /*
                    pretestHC4(curLecture,_JthRPPair);
                    pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                    pretestHC2(curLecture,_JthRPPair); // always satisfied
                    pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                    */

                    if (HC4)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);

                      logger.debug(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"test,relax");
                      HC3VioRelaxedCandidates.add(_IJTriple);

                      break;
                    }
                }

            }

            // Round 4: relax HC4 & relax HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    if (true)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);

                      logger.debug(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"relax,relax");
                      HC4HC3VioRelaxedCandidates.add(_IJTriple);

                      break;
                    }
                }

            }


            if (_LecturesDomain.size()==0) HC1 = true; // HC1

            if (_R_P_Pairs.size()==0)
            {
                logger.warn("More Rooms in need");
                logger.warn("Courses which can not yet be scheduled:");
                for(String NotDoneCourse:_LecturesDomain)
                {
                    int NDCOurseIndex = Integer.parseInt(NotDoneCourse);
                    logger.warn(this.CourseDomain.get(NDCOurseIndex).getCourseID());
                }

                break;
            }

            interation++;
            if (interation > interationMax) break;
        }


        CP_R_Solution = convertTriples2Array(L_R_P_Triples);

    }

    private int[][] generateLRPTriples(double chlevel, int [][] pre_CP_R_Solution, int ... involved_courses)
    {
        logger.debug("###############################");
        logger.debug("pre_CP_R_Solution,level,courses");
        int [][]result = null;

        

        // local variables
        ArrayList<Pair> _R_P_Pairs = new ArrayList();
        // copy all objects from R_P_Pairs to local variable
        for (int i=0;i<R_P_Pairs.size();i++)
        {
            Pair IthPair = R_P_Pairs.get(i);

            _R_P_Pairs.add(new Pair(IthPair.getFirst(),IthPair.getSecond()));
        }


        ArrayList<String> _LecturesDomain = new ArrayList();
        // copy all objects from LecturesDomain to local variable
        for (int i_1=0;i_1<LecturesDomain.size();i_1++)
        {
            String IthString = LecturesDomain.get(i_1);
            _LecturesDomain.add(IthString);
            logger.debug("Size of _LecturesDomain"+_LecturesDomain.size());
            logger.debug("_LecturesDomain:"+IthString+" added");
        }

        // Parameterizise generator for first solution
        // -------------------------------------------
        boolean success= false;
        boolean timeout= false;
        int interation = 0;
        int interationMax = _R_P_Pairs.size()*LecturesDomain.size();

        boolean HC1 = false; // AllLectures
        boolean HC2 = true; // RoomOccupancy
        boolean HC3 = true; // TeacherCurriculumConflicts
        boolean HC4 = true; // Availabilities

        boolean SC1 = true; // RoomCapacity
        boolean SC2 = true; // MinimumWorkingDays
        boolean SC3 = true; // CurriculumCompactness
        boolean SC4 = true; // RoomStability
        // --------------------------------------------

        

        boolean same_pos_relax = false;

        while (!HC1) // || !timeout
        {
            String curLecture = _LecturesDomain.get(0);
            int curLectureIndex = Integer.parseInt(curLecture);
            int pre_LecturesDomainSize = _LecturesDomain.size();

            boolean same_pos_wPre = false;
            
            int reached_chlevel=0;
            int changed_num=0;

            // Constructing Triple - a piece in Timetable
            // Round 1: test both HC3 HC4
            for (int j=0;j<_R_P_Pairs.size();j++)
            {
                Pair _JthRPPair = _R_P_Pairs.get(j);

                HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts

                int _JthRP_Period = Integer.parseInt(String.valueOf(_JthRPPair.getSecond()));

                //logger.debug(curLectureIndex+","+involved_courses.length);

                /*
                System.out.println("DANG TEST "+inArray(curLectureIndex, involved_courses)+","
                        +involved_courses[0]+":"
                        +curLectureIndex+","+_JthRP_Period);
                 */

                if (inArray(curLectureIndex, involved_courses)
                    && pre_CP_R_Solution[curLectureIndex][_JthRP_Period]>-1
                    && reached_chlevel < chlevel)
                {
                    same_pos_wPre = true;
                }
                if (same_pos_relax) same_pos_wPre = false;


                /*
                pretestHC4(curLecture,_JthRPPair);
                pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                pretestHC2(curLecture,_JthRPPair); // always satisfied
                pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                */

                if (HC4 && HC3 && !same_pos_wPre)
                {
                  Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                  this.L_R_P_Triples.add(_IJTriple);

                  _LecturesDomain.remove(0);
                  _R_P_Pairs.remove(j);
                  changed_num++;
                  reached_chlevel = this.CourseDomain.get(curLectureIndex).getLectureNum() / changed_num;

                  logger.info(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"test,test");
                  break;
                }
            }

            

            // Round 2: relax HC4 & test HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    // HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                    HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts

                    int _JthRP_Period = Integer.parseInt(String.valueOf(_JthRPPair.getSecond()));
                    if (inArray(curLectureIndex, involved_courses)
                        && pre_CP_R_Solution[curLectureIndex][_JthRP_Period]>-1
                        && reached_chlevel < chlevel)
                    {
                        same_pos_wPre = true;
                    }
                    if (same_pos_relax) same_pos_wPre = false;

                    /*
                    pretestHC4(curLecture,_JthRPPair);
                    pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                    pretestHC2(curLecture,_JthRPPair); // always satisfied
                    pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                    */

                    if (HC3 && !same_pos_wPre)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);
                      changed_num++;
                      reached_chlevel = this.CourseDomain.get(curLectureIndex).getLectureNum() / changed_num;

                      logger.info(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"relax,test");
                      break;
                    }
                }

            }


            // Round 3: test HC4 & relax HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    HC4 = pretestHC4(curLecture,_JthRPPair); // TeacherAvailabilities
                    //HC3 = pretestHC3(curLecture,_JthRPPair,L_R_P_Triples); // TeacherCurriculumConflicts

                    int _JthRP_Period = Integer.parseInt(String.valueOf(_JthRPPair.getSecond()));
                    if (inArray(curLectureIndex, involved_courses)
                        && pre_CP_R_Solution[curLectureIndex][_JthRP_Period]>-1
                        && reached_chlevel < chlevel)
                    {
                        same_pos_wPre = true;
                    }
                    if (same_pos_relax) same_pos_wPre = false;

                    /*
                    pretestHC4(curLecture,_JthRPPair);
                    pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
                    pretestHC2(curLecture,_JthRPPair); // always satisfied
                    pretestHC1(curLecture); // when halt on at one lecture then jump to the next lecture
                    */

                    if (HC4 && !same_pos_wPre)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);
                      changed_num++;
                      reached_chlevel = this.CourseDomain.get(curLectureIndex).getLectureNum() / changed_num;

                      logger.info(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"test,relax");
                      break;
                    }
                }

            }

            // Round 4: relax HC4 & relax HC3
            if (pre_LecturesDomainSize == _LecturesDomain.size())
            {
                for (int j=0;j<_R_P_Pairs.size();j++)
                {
                    Pair _JthRPPair = _R_P_Pairs.get(j);

                    int _JthRP_Period = Integer.parseInt(String.valueOf(_JthRPPair.getSecond()));
                    if (inArray(curLectureIndex, involved_courses)
                        && pre_CP_R_Solution[curLectureIndex][_JthRP_Period]>-1
                        && reached_chlevel < chlevel)
                    {
                        same_pos_wPre = true;
                    }
                    if (same_pos_relax) same_pos_wPre = false;

                    if (!same_pos_wPre)
                    {
                      Triple _IJTriple = new Triple(curLecture,_JthRPPair.getFirst(),_JthRPPair.getSecond()); // Assign

                      this.L_R_P_Triples.add(_IJTriple);

                      _LecturesDomain.remove(0);
                      _R_P_Pairs.remove(j);
                      changed_num++;
                      reached_chlevel = this.CourseDomain.get(curLectureIndex).getLectureNum() / changed_num;

                      logger.info(curLecture+"assigned with " +_JthRPPair.getFirst()+"-"+_JthRPPair.getSecond()+
                                    "<- HC4,HC3:"+"relax,relax");
                      break;
                    }
                }

            }

            if (pre_LecturesDomainSize == _LecturesDomain.size()) same_pos_relax = true;
            else same_pos_relax = false;

            if (_LecturesDomain.size()==0) HC1 = true; // HC1

            if (_R_P_Pairs.size()==0)
            {
                logger.warn("More Rooms in need");
                logger.warn("Courses which can not yet be scheduled:");
                for(String NotDoneCourse:_LecturesDomain)
                {
                    int NDCOurseIndex = Integer.parseInt(NotDoneCourse);
                    logger.warn(this.CourseDomain.get(NDCOurseIndex).getCourseID());
                }

                break;
            }

            interation++;
            if (interation > interationMax) break;
        }


        result = convertTriples2Array(L_R_P_Triples);

        return result;

    }

   
    private boolean pretestHC3(String _curLecture, Pair _RPPair, List<Triple> _curTriples)
    {
       boolean satisfied= true;

       int CourseIndex = Integer.parseInt(_curLecture);
       Course curCourse = CourseDomain.get(CourseIndex);

       int PeriodIndex = Integer.parseInt(_RPPair.getSecond().toString());


       // calculate array of courses at the same period
       ArrayList<Triple> TriplesAtPeriod= new ArrayList<Triple>();

       for (Triple IthTriple:this.L_R_P_Triples)
       {
           int IthPeriodIndex = Integer.parseInt(IthTriple.getThird().toString());
           if (IthPeriodIndex==PeriodIndex) TriplesAtPeriod.add(IthTriple);
       }

       // test if this course has the same teacher
       // with courses existing in L_R_P_Triples  at the same period
       for (Triple IthTriple:TriplesAtPeriod)
       {
           int IthCourseIndex = Integer.parseInt(IthTriple.getFirst().toString());

           
           Course IthCourse = CourseDomain.get(IthCourseIndex);

           if (IthCourse.getTeacher().equals(curCourse.getTeacher())) return false;

           int originalCourseIndex = Cs_sortedCs_Mapping[CourseIndex];
           int originalIthCourseIndex = Cs_sortedCs_Mapping[IthCourseIndex];

           if (this._inputReader.HardCurriculum_Matrix[originalCourseIndex][originalIthCourseIndex]==1) return false;
           
       }

        return satisfied;
    }

    private boolean pretestHC4(String _curLecture, Pair _RPPair)
    {
       boolean satisfied= true;

       int CourseIndex = Integer.parseInt(_curLecture);
       int PeriodIndex = Integer.parseInt(_RPPair.getSecond().toString());

       // test if the teacher of this course is available
       // at the same period
       
       int originalCourseIndex = Cs_sortedCs_Mapping[CourseIndex];

       if (this._inputReader.C_P_UnavailabilityMatrix[originalCourseIndex][PeriodIndex]!=1)
                satisfied = false;

       return satisfied;
    }

    // RoomCapacity
    public boolean pretestSC2(String _curLecture, Pair _RPPair)
    {
       boolean satisfied= false;

       int CourseIndex = Integer.parseInt(_curLecture);
       int RoomIndex = Integer.parseInt(_RPPair.getFirst().toString());

       

       satisfied = this.CourseDomain.get(CourseIndex).getStudentNum()<=this.RoomDomain.get(RoomIndex).getCapacity();


       return satisfied;
    }


    // 3. hill climbing
    public void HillClimbIterate(int NIIteration)
    {
         generateLRPTriples();
         this.Best_HC_OF = returnViolationsOF(CP_R_Solution);
         Best_CP_R_Solution = this.CP_R_Solution;

         System.out.println("");
         printPenalties(this.CP_R_Solution,this.assigned_ObjFunc);
         best_ObjFunc = this.assigned_ObjFunc;

         /*
         PernaltyCounter _myPenaltyCounter = new PernaltyCounter();
         this.attachTo(_myPenaltyCounter);
          * 
          */

         int [][] _CP_R_Solution = Best_CP_R_Solution;
         int N = this.CP_R_Solution.length;
         int _iteration = 0;

         while (_iteration<NIIteration)
         {
             _CP_R_Solution = generateLRPTriples(Math.random(),_CP_R_Solution,retRandNum(N-1));
             int curOF = returnViolationsOF(_CP_R_Solution);

             System.out.println("");
             printPenalties(_CP_R_Solution);

             if (curOF>this.Best_HC_OF) _iteration++;
             else
             {
                 this.Best_CP_R_Solution = _CP_R_Solution;
                 this.Best_HC_OF = curOF;
                 _iteration=0;
             }
         }

         

    }


    // 4. Utilities
    private boolean inArray(int value, int [] _array)
    {
        for (int v:_array)
        {
            if (value==v) return true;
        }

        return false;
    }

    public int returnViolationsOF(int [][] _Solution)
    {
        PernaltyCounter _PenaltyCounter = new PernaltyCounter();
        this.attachTo(_PenaltyCounter);
        _PenaltyCounter.CP_R_Solution = _Solution;

        int w1=30, w2=20, w3=20, w4=10;

        return w1*_PenaltyCounter.AllLecturesOnTT()+
               w2*_PenaltyCounter.RoomOccupancy()+
               w3*_PenaltyCounter.sameCurriculumLecturesConflict(_inputReader.HardCurriculum_Matrix, this.Cs_sortedCs_Mapping)+
               w3*_PenaltyCounter.sameTeacherLecturesConflict(_inputReader.Courses_waTeacher_Matrix, this.Cs_sortedCs_Mapping)+
               w4*_PenaltyCounter.TeacherAvailabilities(_inputReader.C_P_UnavailabilityMatrix, this.Cs_sortedCs_Mapping)
               ;
    }

    private void printPenalties()
    {
         PernaltyCounter _myPenaltyCounter = new PernaltyCounter();
         this.attachTo(_myPenaltyCounter);

         System.out.println("AllLecturesOnTT() "+_myPenaltyCounter.AllLecturesOnTT());
         System.out.println("RoomOccupancy() "+_myPenaltyCounter.RoomOccupancy());
         System.out.println("sameCurriculumLecturesConflict() "+_myPenaltyCounter.sameCurriculumLecturesConflict(_inputReader.HardCurriculum_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("sameTeacherLecturesConflict() "+_myPenaltyCounter.sameTeacherLecturesConflict(_inputReader.Courses_waTeacher_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("TeacherAvailabilities() "+_myPenaltyCounter.TeacherAvailabilities(_inputReader.C_P_UnavailabilityMatrix, this.Cs_sortedCs_Mapping));


         
         System.out.println("RoomCapacity() "+_myPenaltyCounter.RoomCapacity());
         System.out.println("MinimumWorkingDays() "+_myPenaltyCounter.MinimumWorkingDays(_inputReader.Periods_per_day));
         System.out.println("CurriculumCompactness() "+_myPenaltyCounter.CurriculumCompactness());
         System.out.println("RoomStability() "+_myPenaltyCounter.RoomStability());
    }

    private void printPenalties(int [][] _sol)
    {
         PernaltyCounter _PenaltyCounter = new PernaltyCounter();
        this.attachTo(_PenaltyCounter);
        _PenaltyCounter.CP_R_Solution = _sol;

         System.out.println("AllLecturesOnTT() "+_PenaltyCounter.AllLecturesOnTT());
         System.out.println("RoomOccupancy() "+_PenaltyCounter.RoomOccupancy());
         System.out.println("sameCurriculumLecturesConflict() "+_PenaltyCounter.sameCurriculumLecturesConflict(_inputReader.HardCurriculum_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("sameTeacherLecturesConflict() "+_PenaltyCounter.sameTeacherLecturesConflict(_inputReader.Courses_waTeacher_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("TeacherAvailabilities() "+_PenaltyCounter.TeacherAvailabilities(_inputReader.C_P_UnavailabilityMatrix, this.Cs_sortedCs_Mapping));

         System.out.println("RoomCapacity() "+_PenaltyCounter.RoomCapacity());
         System.out.println("MinimumWorkingDays() "+_PenaltyCounter.MinimumWorkingDays(_inputReader.Periods_per_day));
         System.out.println("CurriculumCompactness() "+_PenaltyCounter.CurriculumCompactness());
         System.out.println("RoomStability() "+_PenaltyCounter.RoomStability());
    }

    private void printPenalties(int [][] _sol, ObjectiveFunction _ObFunc)
    {
        PernaltyCounter _PenaltyCounter = new PernaltyCounter();
        this.attachTo(_PenaltyCounter);
        _PenaltyCounter.CP_R_Solution = _sol;

        if (_ObFunc==null)
        {
            _ObFunc = new ObjectiveFunction();
        }

        _ObFunc.assigned_Solution = _sol;

        _ObFunc.HC1 = _PenaltyCounter.AllLecturesOnTT();
         _ObFunc.HC2 = _PenaltyCounter.RoomOccupancy();
         _ObFunc.HC3 = _PenaltyCounter.sameCurriculumLecturesConflict(_inputReader.HardCurriculum_Matrix, this.Cs_sortedCs_Mapping)
                         +_PenaltyCounter.sameTeacherLecturesConflict(_inputReader.Courses_waTeacher_Matrix, this.Cs_sortedCs_Mapping);
         _ObFunc.HC4 = _PenaltyCounter.TeacherAvailabilities(_inputReader.C_P_UnavailabilityMatrix, this.Cs_sortedCs_Mapping);

         _ObFunc.SC1 = _PenaltyCounter.RoomCapacity();
         _ObFunc.SC2 = _PenaltyCounter.MinimumWorkingDays(_inputReader.Periods_per_day);
         _ObFunc.SC3 = _PenaltyCounter.CurriculumCompactness();
         _ObFunc.SC4 = _PenaltyCounter.RoomStability();

         
         System.out.println("AllLecturesOnTT() "+_PenaltyCounter.AllLecturesOnTT());
         System.out.println("RoomOccupancy() "+_PenaltyCounter.RoomOccupancy());
         System.out.println("sameCurriculumLecturesConflict() "+_PenaltyCounter.sameCurriculumLecturesConflict(_inputReader.HardCurriculum_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("sameTeacherLecturesConflict() "+_PenaltyCounter.sameTeacherLecturesConflict(_inputReader.Courses_waTeacher_Matrix, this.Cs_sortedCs_Mapping));
         System.out.println("TeacherAvailabilities() "+_PenaltyCounter.TeacherAvailabilities(_inputReader.C_P_UnavailabilityMatrix, this.Cs_sortedCs_Mapping));

         System.out.println("RoomCapacity() "+_PenaltyCounter.RoomCapacity());
         System.out.println("MinimumWorkingDays() "+_PenaltyCounter.MinimumWorkingDays(_inputReader.Periods_per_day));
         System.out.println("CurriculumCompactness() "+_PenaltyCounter.CurriculumCompactness());
         System.out.println("RoomStability() "+_PenaltyCounter.RoomStability());
    }

    private int retRandNum(int N)
    {
        double randNum = Math.random();

        for (int i=0;i<=N;i++)
        {
            double rateNum = i/N;
            if (randNum<=rateNum) return i;
        }

        return 0;
    }

    public int [][] convertTriples2Array(List<Triple> _L_R_P_Triples)
    {
        int ArraySol [][]=new int[_inputReader.CNum][_inputReader.DNum*_inputReader.Periods_per_day];

        for (int i=0;i<ArraySol.length;i++)
        {
            for (int j=0;j<ArraySol[0].length;j++)
            {
                ArraySol[i][j]=-1;
            }
        }

        
        for(Triple IthTriple:_L_R_P_Triples)
        {
            /*
            int c = Integer.parseInt(IthTriple.getFirst().toString());
            int p = Integer.parseInt(IthTriple.getThird().toString());
            int r = Integer.parseInt(IthTriple.getSecond().toString());
             *
             */
            logger.info("convertTriples2Array:"+IthTriple.getFirst()+
                                            ","+IthTriple.getSecond()+
                                            ","+IthTriple.getThird());

            int c = Integer.parseInt(String.valueOf(IthTriple.getFirst()));
            int p = Integer.parseInt(String.valueOf(IthTriple.getThird()));
            int r = Integer.parseInt(String.valueOf(IthTriple.getSecond()));

            logger.info("convertTriples2Array:c-r-p"+
                                                c+
                                            ","+r+
                                            ","+p);

            ArraySol[c][p]=r;
            
        }


        return ArraySol;
    }

    public List<Triple> convertArray2Triples(int [][] _ArraySol)
    {
         List<Triple> _L_R_P_Triples=new ArrayList<Triple>();

        for (int i=0;i<_ArraySol.length;i++)
        {
            for (int j=0;j<_ArraySol[0].length;j++)
            {
                if (_ArraySol[i][j]!=-1)
                {
                  Triple newTriple = new Triple();

                  newTriple.setFirst(String.valueOf(i));
                  newTriple.setThird(String.valueOf(j));
                  newTriple.setSecond(String.valueOf(_ArraySol[i][j]));

                  _L_R_P_Triples.add(newTriple);
                }
            }
        }

        return _L_R_P_Triples;

    }



    public void printSolMatrix()
    {
        System.out.println("CP_R_Solution");
        for (int i=0;i< CP_R_Solution.length;i++)
        {
            //int originalIndex
            System.out.print(CourseDomain.get(i).getCourseID()+": ");

            for (int j=0;j< CP_R_Solution[i].length;j++)
            {
                System.out.print(CP_R_Solution[i][j]+" ");
            }

            System.out.println(" ");

        }
    }

    public void attachTo(PernaltyCounter _pc)
    {
        _pc.CourseDomain = this.CourseDomain; // not this._inputReader.Courses
        _pc.Curricula = this._inputReader.Curricula;
        _pc.DayNum = this._inputReader.DNum;
        _pc.periods_per_day = this._inputReader.Periods_per_day;

        _pc.RoomDomain = this.RoomDomain;
        _pc.CP_R_Solution = this.CP_R_Solution;

        /*
        Curriculum IthCurriculum = _pc.Curricula.get(0);

        System.out.println(IthCurriculum.CourseIDs.get(0));
        System.out.println(IthCurriculum.CIndexList.get(0));
         * 
         */

        //_pc.printSolMatrix();


    }

    public void showSolutioninChart()
    {

        // A scatter plot displays data from an XYDataset.
        // In this example, the renderer is changed to an XYDotRenderer

        // create a dataset...
        List<XYSeries> SerieArray = new ArrayList<XYSeries>();

         for (int i=0;i< this.CourseDomain.size();i++)
         {
             XYSeries IthS = new XYSeries(CourseDomain.get(i).getCourseID());

             SerieArray.add(IthS);
         }





        for (int i=0;i< this.CP_R_Solution.length;i++)
        {
            //System.out.print(myInputReader.Courses.get(i).getCourseID()+": ");

            for (int j=0;j< this.CP_R_Solution[i].length;j++)
            {
                //System.out.print(myInputReader.HardCurriculum_Matrix[i][j]+" ");
                if (this.CP_R_Solution[i][j]>-1) SerieArray.get(i).add(j, this.CP_R_Solution[i][j]);
            }



        }




        XYSeriesCollection dataset = new XYSeriesCollection();

        for (int i=0;i< SerieArray.size();i++)
         {
             XYSeries IthS = SerieArray.get(i);

             dataset.addSeries(IthS);
         }

        // create a chart...

        /*
        JFreeChart chart2 = ChartFactory.createScatterPlot(
            "First Solution",          // chart title
            "Period",               // domain axis label
            "Room",                  // range axis label
            dataset,                  // data
            PlotOrientation.VERTICAL,
            true,                     // include legend
            true,
            false
        );

        // create and display a frame...
        ChartFrame frame2 = new ChartFrame("First Solution", chart2);
        frame2.pack();
        frame2.setVisible(true);
         * 
         */

        CustomPlotChart chartFrame = new CustomPlotChart("First Solution "+this._inputReader.InputFileName,"Period("+_inputReader.DNum+"x"+_inputReader.Periods_per_day+")"
                    +"        OF:"+this.assigned_ObjFunc.HC1+"+"+
                                this.assigned_ObjFunc.HC2+"+"+
                                this.assigned_ObjFunc.HC3+"+"+
                                this.assigned_ObjFunc.HC4+" + "+
                                this.assigned_ObjFunc.SC1+"+"+
                                this.assigned_ObjFunc.SC2+"+"+
                                this.assigned_ObjFunc.SC3+"+"+
                                this.assigned_ObjFunc.SC4+"=>"+
                                this.assigned_ObjFunc.value(),"Room",dataset,"C:/FS_out"+this._inputReader.InputFileName+"_FirstSol.png");
        
        chartFrame.setChartText(this.assigned_ObjFunc.HC1+"+"+
                                this.assigned_ObjFunc.HC2+"+"+
                                this.assigned_ObjFunc.HC3+"+"+
                                this.assigned_ObjFunc.HC4+" + "+
                                this.assigned_ObjFunc.SC1+"+"+
                                this.assigned_ObjFunc.SC2+"+"+
                                this.assigned_ObjFunc.SC3+"+"+
                                this.assigned_ObjFunc.SC4+"=>"+
                                this.assigned_ObjFunc.value()
                                );
	chartFrame.pack();
	chartFrame.setVisible(true);
        

       

        if (!chartFrame.isShowing())
        {
          
           
            //chartFrame.saveChartAsPNG("C:/FS_out"+this._inputReader.InputFileName+"_FirstSol.png");
          /*
          }catch (Exception e)
          {
            System.out.println(e);
          }
           * 
           */
        }

    }


    public void showBestSolinChart()
    {

        // A scatter plot displays data from an XYDataset.
        // In this example, the renderer is changed to an XYDotRenderer

        // create a dataset...
        List<XYSeries> SerieArray = new ArrayList<XYSeries>();

         for (int i=0;i< this.CourseDomain.size();i++)
         {
             XYSeries IthS = new XYSeries(CourseDomain.get(i).getCourseID());

             SerieArray.add(IthS);
         }





        for (int i=0;i< this.Best_CP_R_Solution.length;i++)
        {

            for (int j=0;j< this.Best_CP_R_Solution[i].length;j++)
            {
                //System.out.print(myInputReader.HardCurriculum_Matrix[i][j]+" ");
                if (this.Best_CP_R_Solution[i][j]>-1) SerieArray.get(i).add(j, this.Best_CP_R_Solution[i][j]);
            }

        }




        XYSeriesCollection dataset = new XYSeriesCollection();

        for (int i=0;i< SerieArray.size();i++)
         {
             XYSeries IthS = SerieArray.get(i);

             dataset.addSeries(IthS);
         }


        /*

        // create a chart...

        JFreeChart chart2 = ChartFactory.createScatterPlot(
            "Best First Solution",          // chart title
            "Period",               // domain axis label
            "Room",                  // range axis label
            dataset,                  // data
            PlotOrientation.VERTICAL,
            true,                     // include legend
            true,
            true
        );

        // create and display a frame...
        ChartFrame frame2 = new ChartFrame("Best Solution", chart2);
        frame2.pack();
        frame2.setVisible(true);
         * 
         */


        CustomPlotChart chartFrame = new CustomPlotChart("Best Solution","Period","Room",dataset,"C:/FS_out"+this._inputReader.InputFileName+"_BFirstSol.png");

	chartFrame.pack();
	chartFrame.setVisible(true);


        
    }
  
    public void writeSolDown()
    {
        SolWriter _solWriter = new SolWriter("C:/FS_out"+this._inputReader.InputFileName+"_FirstSol.sol");

        _solWriter.Courses = this.CourseDomain;
        _solWriter.Rooms = this.RoomDomain;
        _solWriter.Solution = this.CP_R_Solution;

        _solWriter.DayNum = this._inputReader.DNum;
        _solWriter.Periods_per_Day = this._inputReader.Periods_per_day;

        _solWriter.writeSolDown();


        

    }

    public void writeBestSolDown()
    {
        SolWriter _solWriter = new SolWriter("C:/FS_out"+this._inputReader.InputFileName+"_BestFirstSol.sol");

        _solWriter.Courses = this.CourseDomain;
        _solWriter.Rooms = this.RoomDomain;
        _solWriter.Solution = this.Best_CP_R_Solution;

        _solWriter.DayNum = this._inputReader.DNum;
        _solWriter.Periods_per_Day = this._inputReader.Periods_per_day;

        _solWriter.writeSolDown();

    }

}
